﻿// <copyright file="CampaignFinance.cs" company="Microsoft Corporation">
// Copyright (c) 2009 Microsoft Corporation All Rights Reserved
// </copyright>
// <author>Michael S. Scherotter</author>
// <email>mischero@microsoft.com</email>
// <date>2009-06-10</date>
// <summary>CampaignFinance class definition</summary>

namespace NYTimes.Core
{
    using System;
    using System.ComponentModel;
    using System.Globalization;
    using System.Net;
    using System.Windows;
    using System.Xml.Linq;
    using Synergist;    
    
    /// <summary>
    /// The campaign type
    /// </summary>
    public enum CampaignType
    {
        /// <summary>
        /// Presidential campaign
        /// </summary>
        President
    }

    /// <summary>
    /// Campaign finance base class
    /// </summary>
    public abstract class CampaignFinance : INotifyPropertyChanged
    {
        /// <summary>
        /// the campaign finance Application.xaml resources key
        /// </summary>
        private const string CampaignFinanceKey = "NYTCampaignFinanceKey";

        /// <summary>
        /// 50 QPS rate limiting
        /// </summary>
        private static RateLimiting limiting = new RateLimiting("NYTCampaignFinance", 50);

        /// <summary>
        /// the web client
        /// </summary>
        private WebClient client;

        /// <summary>
        /// the api key
        /// </summary>
        private string apiKey;

        /// <summary>
        /// the API result status
        /// </summary>
        private string status;

        /// <summary>
        /// the API result copyright
        /// </summary>
        private string copyright;

        /// <summary>
        /// Initializes a new instance of the CampaignFinance class.
        /// </summary>
        protected CampaignFinance()
        {
            this.Version = "v3";

            if (Application.Current.Resources.Contains(CampaignFinanceKey))
            {
                this.apiKey = Application.Current.Resources[CampaignFinanceKey] as string;
            }
        }

        #region INotifyPropertyChanged Members

        /// <summary>
        /// Property changed event handler
        /// </summary>
        public event PropertyChangedEventHandler PropertyChanged;

        #endregion

        /// <summary>
        /// Gets the API version
        /// </summary>
        public string Version { get; private set; }

        /// <summary>
        /// Gets or sets the API key
        /// </summary>
        public string ApiKey
        {
            get
            {
                return this.apiKey;
            }

            set
            {
                this.apiKey = value;

                this.FetchData();
            }
        }

        /// <summary>
        /// Gets or sets the campaign type. (Currently only President is available and is the 
        /// default value)
        /// </summary>
        public CampaignType CampaignType { get; set; }

        /// <summary>
        /// Gets the result status
        /// </summary>
        /// <remarks>Once the results are loaded successfully, the status should change to "OK"</remarks>
        public string Status
        {
            get
            {
                return this.status;
            }

            private set
            {
                if (this.status != value)
                {
                    this.status = value;

                    this.TriggerPropertyChanged("Status");
                }
            }
        }

        /// <summary>
        /// Gets the copyright information
        /// </summary>
        public string Copyright
        {
            get
            {
                return this.copyright;
            }

            private set
            {
                if (this.copyright != value)
                {
                    this.copyright = value;

                    this.TriggerPropertyChanged("Copyright");
                }
            }
        }

        /// <summary>
        /// Gets a value indicating whether the CLR object can fetch data
        /// </summary>
        protected virtual bool CanFetchData
        {
            get
            {
                return true;
            }
        }

        /// <summary>
        /// Trigger a property changed event
        /// </summary>
        /// <param name="propertyName">the property name</param>
        protected void TriggerPropertyChanged(string propertyName)
        {
            if (this.PropertyChanged != null)
            {
                this.PropertyChanged(this, new PropertyChangedEventArgs(propertyName));
            }
        }

        /// <summary>
        /// Fetch the data
        /// </summary>
        protected void FetchData()
        {
            if (SampleData.IsInDesignTool)
            {
                return;
            }

            if (string.IsNullOrEmpty(this.ApiKey))
            {
                return;
            }

            if (string.IsNullOrEmpty(this.Version))
            {
                return;
            }

            if (!this.CanFetchData)
            {
                return;
            }

            if (this.client != null)
            {
                this.client.CancelAsync();

                limiting.Cancel(this.client);
            }

            this.client = new WebClient();

            this.client.DownloadStringCompleted += new DownloadStringCompletedEventHandler(this.OnDownloadStringCompleted);

            var address = this.GenerateFullAddress();

            this.Status = string.Empty;

            this.ClearResults();

            limiting.MakeWebRequest(this.client, address);
        }

        /// <summary>
        /// Clear the results
        /// </summary>
        protected abstract void ClearResults();

        /// <summary>
        /// Generate the web service address
        /// </summary>
        /// <param name="baseAddress">the base address</param>
        /// <returns>a full URI to the web service</returns>
        protected abstract Uri GenerateAddress(string baseAddress);

        /// <summary>
        /// Process the results
        /// </summary>
        /// <param name="results">the results element</param>
        /// <returns>true to signify that there are no more results, false otherwise</returns>
        protected abstract bool ProcessResults(XElement results);

        /// <summary>
        /// Parse the XML
        /// </summary>
        /// <param name="xml">the XML from the web service</param>
        protected void ParseXml(string xml)
        {
            if (string.IsNullOrEmpty(xml))
            {
                return;
            }

            var doc = XDocument.Parse(xml);

            var resultSet = doc.Element("result_set");

            if (resultSet == null)
            {
                this.Status = "Incorrect XML results : expecting <result_set>.";

                return;
            }

            var results = resultSet.Element("results");

            if (this.ProcessResults(results))
            {
                this.Copyright = resultSet.Element("copyright").Value;
                this.Status = resultSet.Element("status").Value;
            }
        }

        /// <summary>
        /// Generate the full address
        /// </summary>
        /// <returns>the full address</returns>
        [System.Diagnostics.CodeAnalysis.SuppressMessage("Microsoft.Globalization", "CA1308:NormalizeStringsToUppercase", Justification = "Not relevant for Silverlight.")]
        private Uri GenerateFullAddress()
        {
            string address = string.Format(
                CultureInfo.InvariantCulture,
                "http://api.nytimes.com/svc/elections/us/{0}/finances/",
                this.Version.ToLower(CultureInfo.InvariantCulture));

            return this.GenerateAddress(address);
        }

        /// <summary>
        /// handle the download completion
        /// </summary>
        /// <param name="sender">the web client</param>
        /// <param name="e">the download string completed event arguments</param>
        private void OnDownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
        {
            if (e.Cancelled)
            {
                return;
            }

            this.client = null;

            if (e.Error != null)
            {
                WebException we = e.Error as WebException;

                if (we == null)
                {
                    this.Status = e.Error.Message;
                }
                else
                {
                    this.Status = we.Status.ToString();
                }

                return;
            }

            this.ParseXml(e.Result);
        }
    }
}
